#  ICE Revision: $Id$
"""
Application class that implements pyFoamTimelinePlot.py
"""

import sys
from os import path
from optparse import OptionGroup

from .PyFoamApplication import PyFoamApplication
from PyFoam.RunDictionary.TimelineDirectory import TimelineDirectory
from PyFoam.Basics.SpreadsheetData import WrongDataSize
from PyFoam.ThirdParty.six import print_

from .PlotHelpers import cleanFilename

class TimelinePlot(PyFoamApplication):
    def __init__(self,
                 args=None,
                 **kwargs):
        description="""\
Searches a directory for timelines that were generated by some
functionObject and generates the commands to gnuplot it. As an option
the data can be written to a CSV-file.
        """

        PyFoamApplication.__init__(self,
                                   args=args,
                                   description=description,
                                   usage="%prog [options] <casedir>",
                                   nr=1,
                                   changeVersion=False,
                                   interspersed=True,
                                   **kwargs)

    def addOptions(self):
        data=OptionGroup(self.parser,
                          "Data",
                          "Select the data to plot")
        self.parser.add_option_group(data)

        data.add_option("--fields",
                        action="append",
                        default=None,
                        dest="fields",
                        help="The fields for which timelines should be plotted. All if unset")
        data.add_option("--positions",
                        action="append",
                        default=None,
                        dest="positions",
                        help="The positions for which timelines should be plotted. Either strings or integers (then the corresponding column number will be used). All if unset")
        data.add_option("--write-time",
                        default=None,
                        dest="writeTime",
                        help="If more than one time-subdirectory is stored select which one is used")
        data.add_option("--directory-name",
                        action="store",
                        default="probes",
                        dest="dirName",
                        help="Alternate name for the directory with the samples (Default: %default)")
        data.add_option("--reference-directory",
                        action="store",
                        default=None,
                        dest="reference",
                        help="A reference directory. If fitting timeline data is found there it is plotted alongside the regular data")
        data.add_option("--reference-case",
                        action="store",
                        default=None,
                        dest="referenceCase",
                        help="A reference case where a directory with the same name is looked for. Mutual exclusive with --reference-directory")

        time=OptionGroup(self.parser,
                         "Time",
                         "Select the times to plot")
        self.parser.add_option_group(time)

        time.add_option("--time",
                        action="append",
                        type="float",
                        default=None,
                        dest="time",
                        help="The times that are plotted (can be used more than once). Has to be specified for bars")
        time.add_option("--min-time",
                        action="store",
                        type="float",
                        default=None,
                        dest="minTime",
                        help="The smallest time that should be used for lines")
        time.add_option("--max-time",
                        action="store",
                        type="float",
                        default=None,
                        dest="maxTime",
                        help="The biggest time that should be used for lines")
        time.add_option("--reference-time",
                        action="store_true",
                        default=False,
                        dest="referenceTime",
                        help="Use the time of the reference data for scaling instead of the regular data")


        plot=OptionGroup(self.parser,
                           "Plot",
                           "How data should be plotted")
        self.parser.add_option_group(plot)

        plot.add_option("--basic-mode",
                        type="choice",
                        dest="basicMode",
                        default=None,
                        choices=["bars","lines"],
                        help="Whether 'bars' of the values at selected times or 'lines' over the whole timelines should be plotted")
        vModes=["mag","x","y","z"]
        plot.add_option("--vector-mode",
                        type="choice",
                        dest="vectorMode",
                        default="mag",
                        choices=vModes,
                        help="How vectors should be plotted. By magnitude or as a component. Possible values are "+str(vModes)+" Default: %default")
        plot.add_option("--collect-lines-by",
                        type="choice",
                        dest="collectLines",
                        default="fields",
                        choices=["fields","positions"],
                        help="Collect lines for lineplotting either by 'fields' or 'positions'. Default: %default")

        output=OptionGroup(self.parser,
                           "Output",
                           "Where data should be plotted to")
        self.parser.add_option_group(output)

        output.add_option("--gnuplot-file",
                          action="store",
                          dest="gnuplotFile",
                          default=None,
                          help="Write the necessary gnuplot commands to this file. Else they are written to the standard output")
        output.add_option("--picture-destination",
                          action="store",
                          dest="pictureDest",
                          default=None,
                          help="Directory the pictures should be stored to")
        output.add_option("--name-prefix",
                          action="store",
                          dest="namePrefix",
                          default=None,
                          help="Prefix to the picture-name")
        output.add_option("--clean-filename",
                          action="store_true",
                          dest="cleanFilename",
                          default=False,
                          help="Clean filenames so that they can be used in HTML or Latex-documents")
        output.add_option("--csv-file",
                          action="store",
                          dest="csvFile",
                          default=None,
                          help="Write the data to a CSV-file instead of the gnuplot-commands")
        output.add_option("--excel-file",
                          action="store",
                          dest="excelFile",
                          default=None,
                          help="Write the data to a Excel-file instead of the gnuplot-commands")
        output.add_option("--pandas-data",
                          action="store_true",
                          dest="pandasData",
                          default=False,
                          help="Pass the raw data in pandas-format")
        output.add_option("--numpy-data",
                          action="store_true",
                          dest="numpyData",
                          default=False,
                          help="Pass the raw data in numpy-format")
        output.add_option("--reference-prefix",
                          action="store",
                          dest="refprefix",
                          default="Reference",
                          help="Prefix that gets added to the reference lines. Default: %default")

        data.add_option("--info",
                        action="store_true",
                        dest="info",
                        default=False,
                        help="Print info about the sampled data and exit")
        output.add_option("--resample",
                          action="store_true",
                          dest="resample",
                          default=False,
                          help="Resample the reference value to the current x-axis (for CSV and Excel-output)")
        output.add_option("--extend-data",
                          action="store_true",
                          dest="extendData",
                          default=False,
                          help="Extend the data range if it differs (for CSV and Excel-files)")
        output.add_option("--silent",
                          action="store_true",
                          dest="silent",
                          default=False,
                          help="Don't write to screen (with the silent and the compare-options)")

        numerics=OptionGroup(self.parser,
                             "Quantify",
                             "Metrics of the data and numerical comparisons")
        self.parser.add_option_group(numerics)
        numerics.add_option("--compare",
                            action="store_true",
                            dest="compare",
                            default=None,
                            help="Compare all data sets that are also in the reference data")
        numerics.add_option("--metrics",
                            action="store_true",
                            dest="metrics",
                            default=None,
                            help="Print the metrics of the data sets")
        numerics.add_option("--use-reference-for-comparison",
                            action="store_false",
                            dest="compareOnOriginal",
                            default=True,
                            help="Use the reference-data as the basis for the numerical comparison. Otherwise the original data will be used")

    def setFile(self,fName):
        if self.opts.namePrefix:
            fName=self.opts.namePrefix+"_"+fName
        if self.opts.pictureDest:
            fName=path.join(self.opts.pictureDest,fName)

        name=fName
        if self.opts.cleanFilename:
            name=cleanFilename(fName)
        return 'set output "%s"\n' % name

    def run(self):
        # remove trailing slashif present
        if self.opts.dirName[-1]==path.sep:
            self.opts.dirName=self.opts.dirName[:-1]

        usedDirName=self.opts.dirName.replace("/","_")

        timelines=TimelineDirectory(self.parser.getArgs()[0],
                                    dirName=self.opts.dirName,
                                    writeTime=self.opts.writeTime)
        reference=None
        if self.opts.reference and self.opts.referenceCase:
            self.error("Options --reference-directory and --reference-case are mutual exclusive")
        if (self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData)  and (self.opts.compare or self.opts.metrics):
            self.error("Options --csv-file/excel-file/--pandas-data/--numpy-data and --compare/--metrics are mutual exclusive")

        if self.opts.reference:
            reference=TimelineDirectory(self.parser.getArgs()[0],
                                        dirName=self.opts.reference,
                                        writeTime=self.opts.writeTime)
        elif self.opts.referenceCase:
            reference=TimelineDirectory(self.opts.referenceCase,
                                        dirName=self.opts.dirName,
                                        writeTime=self.opts.writeTime)

        if self.opts.info:
            self.setData({'writeTimes' : timelines.writeTimes,
                          'usedTimes'  : timelines.usedTime,
                          'fields'     : timelines.values,
                          'positions'  : timelines.positions(),
                          'timeRange'  : timelines.timeRange()})

            if not self.opts.silent:
                print_("Write Times    : ",timelines.writeTimes)
                print_("Used Time      : ",timelines.usedTime)
                print_("Fields         : ",timelines.values,end="")
            if len(timelines.vectors)>0:
                if not self.opts.silent:
                    print_(" Vectors: ",timelines.vectors)
                self.setData({'vectors':timelines.vectors})
            else:
                if not self.opts.silent:
                    print_()
            if not self.opts.silent:
                print_("Positions      : ",timelines.positions())
                print_("Time range     : ",timelines.timeRange())

            if reference:
                refData={'writeTimes' : reference.writeTimes,
                         'fields'     : reference.values,
                         'positions'  : reference.positions(),
                         'timeRange'  : reference.timeRange()}

                if not self.opts.silent:
                    print_("\nReference Data")
                    print_("Write Times    : ",reference.writeTimes)
                    print_("Fields         : ",reference.values,end="")
                if len(reference.vectors)>0:
                    if not self.opts.silent:
                        print_(" Vectors: ",reference.vectors)
                    refData["vectors"]=reference.vectors
                else:
                    if not self.opts.silent:
                        print_()
                if not self.opts.silent:
                    print_("Positions      : ",reference.positions())
                    print_("Time range     : ",reference.timeRange())
                self.setData({"reference":refData})

            return 0

        if self.opts.fields==None:
            self.opts.fields=timelines.values
        else:
            for v in self.opts.fields:
                if v not in timelines.values:
                    self.error("The requested value",v,"not in possible values",timelines.values)
        if self.opts.positions==None:
            self.opts.positions=timelines.positions()
        else:
            pos=self.opts.positions
            self.opts.positions=[]
            for p in pos:
                try:
                    p=int(p)
                    if p<0 or p>=len(timelines.positions()):
                        self.error("Time index",p,"out of range for positons",timelines.positions())
                    else:
                        self.opts.positions.append(timelines.positions()[p])
                except ValueError:
                    if p not in timelines.positions():
                        self.error("Position",p,"not in",timelines.positions())
                    else:
                        self.opts.positions.append(p)

        if len(self.opts.positions)==0:
            self.error("No valid positions")

        result="set term png nocrop enhanced \n"

        if self.opts.basicMode==None:
            self.error("No mode selected. Do so with '--basic-mode'")
        elif self.opts.basicMode=='bars':
            if self.opts.time==None:
                self.error("No times specified for bar-plots")
            self.opts.time.sort()
            if self.opts.referenceTime and reference!=None:
                minTime,maxTime=reference.timeRange()
            else:
                minTime,maxTime=timelines.timeRange()
            usedTimes=[]
            hasMin=False
            for t in self.opts.time:
                if t<minTime:
                    if not hasMin:
                        usedTimes.append(minTime)
                        hasMin=True
                elif t>maxTime:
                    usedTimes.append(maxTime)
                    break
                else:
                    usedTimes.append(t)
            data=timelines.getData(usedTimes,
                                   value=self.opts.fields,
                                   position=self.opts.positions,
                                   vectorMode=self.opts.vectorMode)
            #            print_(data)
            result+="set style data histogram\n"
            result+="set style histogram cluster gap 1\n"
            result+="set style fill solid border -1\n"
            result+="set boxwidth 0.9\n"
            result+="set xtics border in scale 1,0.5 nomirror rotate by 90  offset character 0, 0, 0\n"
            # set xtic rotate by -45\n"
            result+="set xtics ("
            for i,p in enumerate(self.opts.positions):
                if i>0:
                    result+=" , "
                result+='"%s" %d' % (p,i)
            result+=")\n"
            for tm in usedTimes:
                if abs(float(tm))>1e20:
                    continue
                result+=self.setFile("%s_writeTime_%s_Time_%s.png"  % (usedDirName,timelines.usedTime,tm))
                result+='set title "Directory: %s WriteTime: %s Time: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,tm)
                result+= "plot "
                first=True
                for val in self.opts.fields:
                    if first:
                        first=False
                    else:
                        result+=", "
                    result+='"-" title "%s" ' % val.replace("_","\\\\_")
                result+="\n"
                for v,t,vals in data:
                    if t==tm:
                        for v in vals:
                            result+="%g\n" % v
                        result+="e\n"
        elif self.opts.basicMode=='lines':
            #            print_(self.opts.positions)
            oPlots=timelines.getDataLocation(value=self.opts.fields,
                                            position=self.opts.positions,
                                            vectorMode=self.opts.vectorMode)

            plots=oPlots[:]
            rPlots=None

            if reference:
                rPlots=reference.getDataLocation(value=self.opts.fields,
                                                 position=self.opts.positions,
                                                 vectorMode=self.opts.vectorMode)
                for gp,pos,val,comp,tv in rPlots:
                    plots.append((gp,
                                  pos,
                                  self.opts.refprefix+" "+val,
                                  comp,
                                  tv))
            if self.opts.referenceTime and reference!=None:
                minTime,maxTime=reference.timeRange()
            else:
                minTime,maxTime=timelines.timeRange()
            if self.opts.minTime:
                minTime=self.opts.minTime
            if self.opts.maxTime:
                maxTime=self.opts.maxTime
            result+= "set xrange [%g:%g]\n" % (minTime,maxTime)
            if self.opts.collectLines=="fields":
                for val in self.opts.fields:
                    vname=val
                    if val in timelines.vectors:
                        vname+="_"+self.opts.vectorMode
                    result+=self.setFile("%s_writeTime_%s_Value_%s.png"  % (usedDirName,timelines.usedTime,vname))
                    result+='set title "Directory: %s WriteTime: %s Value: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,vname.replace("_","\\\\\\_"))
                    result+= "plot "
                    first=True
                    for f,v,p,i,tl in plots:
                        if v==val:
                            if first:
                                first=False
                            else:
                                result+=" , "
                            if type(i)==int:
                                result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,p.replace("_","\\\\_"))
                            else:
                                result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,p.replace("_","\\\\_"))

                    result+="\n"
            elif self.opts.collectLines=="positions":
                for pos in self.opts.positions:
                    result+=self.setFile("%s_writeTime_%s_Position_%s.png"  % (usedDirName,timelines.usedTime,pos))
                    result+='set title "Directory: %s WriteTime: %s Position: %s"\n' % (self.opts.dirName.replace("_","\\\\_"),timelines.usedTime,pos.replace("_","\\\\_"))
                    result+= "plot "
                    first=True
                    for f,v,p,i,tl in plots:
                        if p==pos:
                            if first:
                                first=False
                            else:
                                result+=" , "
                            if type(i)==int:
                                result+= ' "%s" using 1:%d title "%s" with lines ' % (f,i+2,v.replace("_","\\\\_"))
                            else:
                                result+= ' "%s" using 1:%s title "%s" with lines ' % (f,i,v.replace("_","\\\\_"))
                    result+="\n"

            else:
                self.error("Unimplemented collection of lines:",self.opts.collectLines)
        else:
            self.error("Not implemented basicMode",self.opts.basicMode)

        if self.opts.csvFile or self.opts.excelFile or self.opts.pandasData or self.opts.numpyData:
            if self.opts.basicMode!='lines':
                self.error("CSV and Excel-files currently only supported for lines-mode (also Pandas and Numpy-data)")
            spread=plots[0][-1]()
            usedFiles=set([plots[0][0]])
            for line in plots[1:]:
                if line[0] not in usedFiles:
                    usedFiles.add(line[0])
                    sp=line[-1]()
                    try:
                        spread+=sp
                    except WrongDataSize:
                        if self.opts.resample:
                            for n in sp.names()[1:]:
                                data=spread.resample(sp,
                                                     n,
                                                     extendData=self.opts.extendData)
                                try:
                                    spread.append(n,data)
                                except ValueError:
                                    spread.append(self.opts.refprefix+" "+n,data)
                        else:
                            self.warning("Try the --resample-option")
                            raise

            if self.opts.csvFile:
                spread.writeCSV(self.opts.csvFile)
            if self.opts.excelFile:
                spread.getData().to_excel(self.opts.excelFile)
            if self.opts.pandasData:
                self.setData({"series":spread.getSeries(),
                              "dataFrame":spread.getData()})
            if self.opts.numpyData:
                self.setData({"data":spread.data.copy()})

        elif self.opts.compare  or self.opts.metrics:
            statData={}
            if self.opts.compare:
                statData["compare"]={}
            if self.opts.metrics:
                statData["metrics"]={}
            for p in self.opts.positions:
                if self.opts.compare:
                    statData["compare"][p]={}
                if self.opts.metrics:
                    statData["metrics"][p]={}

            if self.opts.basicMode!='lines':
                self.error("Compare currently only supported for lines-mode")

            if self.opts.compare:
                if rPlots==None:
                    self.error("No reference data specified. Can't compare")
                elif len(rPlots)!=len(oPlots):
                    self.error("Number of original data sets",len(oPlots),
                               "is not equal to the reference data sets",
                               len(rPlots))

            for i,p in enumerate(oPlots):
                pth,val,loc,ind,tl=p
                if self.opts.compare:
                    rpth,rval,rloc,rind,rtl=rPlots[i]
                    if val!=rval or loc!=rloc or ind!=rind:
                        self.error("Original data",p,"and reference",rPlots[i],
                                   "do not match")
                data=tl()
                try:
                    dataIndex=1+ind
                    if self.opts.metrics:
                        if not self.opts.silent:
                            print_("Metrics for",val,"on",loc,"index",ind,"(Path:",pth,")")
                        result=data.metrics(data.names()[dataIndex],
                                            minTime=self.opts.minTime,
                                            maxTime=self.opts.maxTime)
                        statData["metrics"][loc][val]=result
                        if not self.opts.silent:
                            print_("  Min                :",result["min"])
                            print_("  Max                :",result["max"])
                            print_("  Average            :",result["average"])
                            print_("  Weighted average   :",result["wAverage"])
                            if not self.opts.compare:
                                print_("Data size:",data.size())
                            print_("  Time Range         :",result["tMin"],result["tMax"])
                    if self.opts.compare:
                        if not self.opts.silent:
                            print_("Comparing",val,"on",loc,"index",ind,"(path:",pth,")",end="")
                        ref=rtl()
                        if self.opts.compareOnOriginal:
                            if not self.opts.silent:
                                print_("on original data points")
                            result=data.compare(ref,
                                                data.names()[dataIndex],
                                                minTime=self.opts.minTime,
                                                maxTime=self.opts.maxTime)
                        else:
                            if not self.opts.silent:
                                print_("on reference data points")
                            result=ref.compare(data,
                                               data.names()[dataIndex],
                                               minTime=self.opts.minTime,
                                               maxTime=self.opts.maxTime)

                        statData["compare"][loc][val]=result
                        if not self.opts.silent:
                            print_("  Max difference     :",result["max"])
                            print_("  Average difference :",result["average"])
                            print_("  Weighted average   :",result["wAverage"])
                            print_("Data size:",data.size(),"Reference:",ref.size())
                            if not self.opts.metrics:
                                print_("  Time Range         :",result["tMin"],result["tMax"])
                    if not self.opts.silent:
                        print_()
                except TypeError:
                    if self.opts.vectorMode=="mag":
                        self.error("Vector-mode 'mag' not supported for --compare and --metrics")
                    else:
                        raise

            self.setData(statData)
        else:
            dest=sys.stdout
            if self.opts.gnuplotFile:
                dest=open(self.opts.gnuplotFile,"w")

            dest.write(result)

# Should work with Python3 and Python2
